package com.ll19.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.compass.annotations.Searchable;
import org.compass.annotations.SearchableComponent;
import org.compass.annotations.SearchableId;
import org.compass.annotations.SearchableProperty;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

/**
 * 
 * 注：USER对象实现了SPRING security 的 UserDetails 接口。
 * 
 * @Entity：指明这是一个Entity Bean
 * @Table为表名，如果不注明name则默认用类名做表名
 * 
 *                              实体bean会根据所有的get方法后的名称对应数据库的字段
 * 
 *                              如果你get后的名称和数据库字段不同，就需要注明@Column(name =
 *                              "password_hint")
 * 
 * 
 */
@Entity
@Table(name = "app_user")
// 全文检索注释 @Searchable(alias="article")表示这个是可以搜索实体，别名为article.
// 声明为搜索实体，映射到lucene中的document
@Searchable
@XmlRootElement
// @Cache(usage = CacheConcurrencyStrategy.NONE)
// 缓存的方式有四种，分别为：
// CacheConcurrencyStrategy.NONE ，不使用缓存
// CacheConcurrencyStrategy.READ_ONLY，只读模式，在此模式下，如果对数据进行更新操作， 会有异常；
// CacheConcurrencyStrategy.READ_WRITE，读写模式在更新缓存的时候会把缓存里面的数据换
// 成一个锁，其它事务如果去取相应的缓存数据，发现被锁了，直接就去数据库查询；
// CacheConcurrencyStrategy.NONSTRICT_READ_WRITE，不严格的读写模式则不会把缓存数据加 锁；
// CacheConcurrencyStrategy.TRANSACTIONAL，事务模式指缓存支持事务，当事务回滚时，缓存 也能回滚，只支持 JTA 环境。
public class User extends BaseObject implements Serializable, UserDetails {

	private static final long serialVersionUID = -7533169433759895305L;
	private Long id;
	private String username; // required
	private String password; // required
	private String confirmPassword;
	private String passwordHint;
	private String firstName; // required
	private String lastName; // required
	private String email; // required; unique
	private String phoneNumber;
	private String website;
	private Address address = new Address();
	private Integer version;
	private Set<Role> roles = new HashSet<Role>();
	private boolean enabled;
	private boolean accountExpired;
	private boolean accountLocked;
	private boolean credentialsExpired;

	/**
	 * Default constructor - creates a new instance with no values set.
	 */
	public User() {
	}

	/**
	 * Create a new instance and set the username.
	 * 
	 * @param username
	 *            login name for user.
	 */
	public User(final String username) {
		this.username = username;
	}

	// @Id：指定表的主键。
	// @GeneratedValue：定义了ID的生成方式，它可以有下列多种生成方式。
	@Id
	// JPA的供应者自行型判断如何生成主键（不同数据库生成方法不同）
	@GeneratedValue(strategy = GenerationType.AUTO)
	// 全文检索注释@SearchableId 这个是实体搜索的标识ID,和hibernate里的概念差不多，用来区分索引文件里的实体索引。
	@SearchableId
	public Long getId() {
		return id;
	}

	// @Column：定义了将成员属性映射到关系表中的哪一列和该列的一些结构信息（如列名是否
	// 唯一，是否允许为空，是否允许更新等），他的属性介绍如下：
	// •name: 映射的列名。如：映射Person 表的PersonName列，可以在name属性的getName方法上面加入@Column(name
	// = "PersonName")，如果不指定映射列名，容器将属性名称作为默认的映射列名。
	// •unique: 是否唯一
	// •nullable: 是否允许为空
	// •length: 对于字符型列，length 属性指定列的最大字符长度
	// •insertable: 是否允许插入
	// •updatable: 是否允许更新
	// •columnDefinition: 定义建表时创建此列的DDL
	// •secondaryTable: 从表名。如果此列不建在主表上（默认建在主表），该属性定义该列所在从表的名字。
	@Column(nullable = false, length = 50, unique = true)
	// 全文检索 @SearchableProperty(index = Index.TOKENIZED, store = Store.YES)
	// 表示这个属性存入索引文件,而且是在分词后在存入.
	@SearchableProperty
	public String getUsername() {
		return username;
	}

	@Column(nullable = false)
	@XmlTransient
	public String getPassword() {
		return password;
	}

	// @Transient标注为不做持久化映射(即ConfirmPasswordb不是数据库的字段)
	@Transient
	@XmlTransient
	public String getConfirmPassword() {
		return confirmPassword;
	}

	// 实体bean会根据所有的get方法后的名称对应数据库的字段
	// 如果你get后的名称和数据库字段不同，就需要注明@Column(name = "password_hint")
	@Column(name = "password_hint")
	@XmlTransient
	public String getPasswordHint() {
		return passwordHint;
	}

	@Column(name = "first_name", nullable = false, length = 50)
	@SearchableProperty
	public String getFirstName() {
		return firstName;
	}

	@Column(name = "last_name", nullable = false, length = 50)
	@SearchableProperty
	public String getLastName() {
		return lastName;
	}

	// email是唯一的
	@Column(nullable = false, unique = true)
	@SearchableProperty
	public String getEmail() {
		return email;
	}

	@Column(name = "phone_number")
	@SearchableProperty
	public String getPhoneNumber() {
		return phoneNumber;
	}

	@SearchableProperty
	public String getWebsite() {
		return website;
	}

	/**
	 * Returns the full name.
	 * 
	 * @return firstName + ' ' + lastName
	 */
	// @Transient标注为不做持久化映射
	@Transient
	public String getFullName() {
		return firstName + ' ' + lastName;
	}

	// @embedded表示该属性的类是嵌入类。
	// 对应的Address中有一个注释为@embeddable是注释Java类的，表示类是嵌入类。
	// Address类中的JPA注释是相同的
	@Embedded
	@SearchableComponent
	public Address getAddress() {
		return address;
	}

	// @OneToOne：一对一映射。它包含五个属性：
	// targetEntity:关联的目标类
	// Cascade:持久化时的级联操作，默认没有
	// fetch:获取对象的方式，默认EAGER
	// Optional:目标对象是否允许为null，默认允许
	// mappedBy:定义双向关联中的从属类。

	// @ManyToMany基本差不多 没有Optional属性

	// 维护端注解 因为角色只在用户维护的时候更新 所以用户实体为维护端
	// 如果你需要在ROLE中维护app_user_role 同样也增加相同映射即可

	// @ManyToMany (cascade = CascadeType.REFRESH)
	// @JoinTable (//关联表
	// name = "student_teacher" , //关联表名
	// inverseJoinColumns = @JoinColumn (name = "teacher_id" ),//被维护端外键
	// joinColumns = @JoinColumn (name = "student_id" ))//维护端外键

	// 多对多关系：(2个类可以互相包含多个对方，多对多有个中间表)
	// 中间表无实体类，只在数据库中存在
	// @ManyToMany
	// @JoinTable(name="中间表名称",
	// joinColumns=@joinColumn(name="本类的外键",
	// referencedColumnName="本类与外键对应的主键"),
	// inversejoinColumns=@JoinColumn(name="对方类的外键",
	// referencedColunName="对方类与外键对应的主键")
	// )
	//

	// FetchType.LAZY：懒加载，加载一个实体时，定义懒加载的属性不会马上从数据库中加载。
	// FetchType.EAGER：急加载，加载一个实体时，定义急加载的属性会立即从数据库中加载。

	// 对于获取关联表 其实可以用FetchType.LAZY
	// 如果只是单表 使用FetchType.LAZY 只有在get 的时候才获取数据的

	@ManyToMany(fetch = FetchType.EAGER)
	@JoinTable(name = "app_user_role", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns = @JoinColumn(name = "role_id"))
	public Set<Role> getRoles() {
		return roles;
	}

	/**
	 * Convert user roles to LabelValue objects for convenience.
	 * 
	 * @return a list of LabelValue objects with role information
	 */
	@Transient
	public List<LabelValue> getRoleList() {
		List<LabelValue> userRoles = new ArrayList<LabelValue>();

		if (this.roles != null) {
			for (Role role : roles) {
				// convert the user's roles to LabelValue Objects
				userRoles.add(new LabelValue(role.getName(), role.getName()));
			}
		}

		return userRoles;
	}

	/**
	 * Adds a role for the user
	 * 
	 * @param role
	 *            the fully instantiated role
	 */
	public void addRole(Role role) {
		getRoles().add(role);
	}

	/**
	 * @return GrantedAuthority[] an array of roles.
	 * @see org.springframework.security.core.userdetails.UserDetails#getAuthorities()
	 */
	@Transient
	public Set<GrantedAuthority> getAuthorities() {
		Set<GrantedAuthority> authorities = new LinkedHashSet<GrantedAuthority>();
		authorities.addAll(roles);
		return authorities;
	}

	// JPA的Version
	// 指定用作其乐观锁定值的实体类的版本字段
	@Version
	public Integer getVersion() {
		return version;
	}

	@Column(name = "account_enabled")
	public boolean isEnabled() {
		return enabled;
	}

	@Column(name = "account_expired", nullable = false)
	public boolean isAccountExpired() {
		return accountExpired;
	}

	/**
	 * @see org.springframework.security.core.userdetails.UserDetails#isAccountNonExpired()
	 * @return true if account is still active
	 */
	@Transient
	public boolean isAccountNonExpired() {
		return !isAccountExpired();
	}

	@Column(name = "account_locked", nullable = false)
	public boolean isAccountLocked() {
		return accountLocked;
	}

	/**
	 * @see org.springframework.security.core.userdetails.UserDetails#isAccountNonLocked()
	 * @return false if account is locked
	 */
	@Transient
	public boolean isAccountNonLocked() {
		return !isAccountLocked();
	}

	@Column(name = "credentials_expired", nullable = false)
	public boolean isCredentialsExpired() {
		return credentialsExpired;
	}

	/**
	 * @see org.springframework.security.core.userdetails.UserDetails#isCredentialsNonExpired()
	 * @return true if credentials haven't expired
	 */
	@Transient
	public boolean isCredentialsNonExpired() {
		return !credentialsExpired;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public void setConfirmPassword(String confirmPassword) {
		this.confirmPassword = confirmPassword;
	}

	public void setPasswordHint(String passwordHint) {
		this.passwordHint = passwordHint;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public void setPhoneNumber(String phoneNumber) {
		this.phoneNumber = phoneNumber;
	}

	public void setWebsite(String website) {
		this.website = website;
	}

	public void setAddress(Address address) {
		this.address = address;
	}

	public void setRoles(Set<Role> roles) {
		this.roles = roles;
	}

	public void setVersion(Integer version) {
		this.version = version;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public void setAccountExpired(boolean accountExpired) {
		this.accountExpired = accountExpired;
	}

	public void setAccountLocked(boolean accountLocked) {
		this.accountLocked = accountLocked;
	}

	public void setCredentialsExpired(boolean credentialsExpired) {
		this.credentialsExpired = credentialsExpired;
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (!(o instanceof User)) {
			return false;
		}

		final User user = (User) o;

		return !(username != null ? !username.equals(user.getUsername()) : user
				.getUsername() != null);

	}

	/**
	 * {@inheritDoc}
	 */
	public int hashCode() {
		return (username != null ? username.hashCode() : 0);
	}

	/**
	 * {@inheritDoc}
	 */
	public String toString() {
		ToStringBuilder sb = new ToStringBuilder(this,
				ToStringStyle.DEFAULT_STYLE).append("username", this.username)
				.append("enabled", this.enabled)
				.append("accountExpired", this.accountExpired)
				.append("credentialsExpired", this.credentialsExpired)
				.append("accountLocked", this.accountLocked);

		if (roles != null) {
			sb.append("Granted Authorities: ");

			int i = 0;
			for (Role role : roles) {
				if (i > 0) {
					sb.append(", ");
				}
				sb.append(role.toString());
				i++;
			}
		} else {
			sb.append("No Granted Authorities");
		}
		return sb.toString();
	}

}
